package com.jshan.common.aspect;

import com.alibaba.fastjson2.JSON;
import com.jshan.common.annotation.OperationLog;
import com.jshan.base.entity.SysOperationLog;
import com.jshan.service.SysOperationLogService;
import com.jshan.service.SysUserService;
import com.jshan.utils.IPUtils;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

/**
 * 系统操作日志，切面处理类
 *
 * @author JShan
 * @since 2024-06-16
 */
@Aspect
@Component
@Slf4j
public class OperationLogAspect {

    @Resource
    private SysOperationLogService sysOperationLogService;
    @Resource
    private SysUserService userService;

    @Pointcut("@annotation(com.jshan.common.annotation.OperationLog)")
    public void logPointCut() {
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result;
        try {
            result = point.proceed();
            saveLog(point, beginTime, 1, null); // 1表示成功
        } catch (Exception e) {
            saveLog(point, beginTime, 0, e.getMessage()); // 0表示失败
            throw e;
        }
        return result;
    }

    private void saveLog(ProceedingJoinPoint joinPoint, long beginTime, int status, String errorMsg) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysOperationLog sysOperationLog = new SysOperationLog();
        OperationLog operationLog = method.getAnnotation(OperationLog.class);
        if (operationLog != null) {
            // 注解上的描述
            sysOperationLog.setOperation(operationLog.value());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysOperationLog.setMethod(className + "." + methodName + "()");
        // 请求的参数
        Object[] args = joinPoint.getArgs();
        try {
            String params = serializeArgs(args);
            sysOperationLog.setParams(params);
        } catch (Exception e) {
            log.error("参数序列化失败: ", e);
        }
        // 获取request
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (attributes != null) {
            request = attributes.getRequest();
            sysOperationLog.setIp(IPUtils.getIpAddr(request));
            sysOperationLog.setUrl(request.getRequestURL().toString());
        }
        // 操作人用户名
        try {
            sysOperationLog.setUsername(userService.getById(StpUtil.getLoginIdAsLong()).getUsername());
        } catch (Exception e) {
            sysOperationLog.setUsername("未知用户"); // 未登录用户或者Sa-Token异常
            log.warn("获取操作人用户名失败: {}", e.getMessage());
        }
        // 执行时长(毫秒)
        long endTime = System.currentTimeMillis();
        sysOperationLog.setTime(endTime - beginTime);
        sysOperationLog.setStatus(status);
        sysOperationLog.setErrorMsg(errorMsg);
        sysOperationLog.setCreateTime(new Date());
        // 保存系统日志
        sysOperationLogService.save(sysOperationLog);
    }

    private String serializeArgs(Object[] args) {
        if (args == null || args.length  == 0) {
            return "[]";
        }
        Object[] serializableArgs = Arrays.stream(args)
                .map(arg -> {
                    // 过滤掉 MultipartFile 类型的参数
                    if (arg instanceof MultipartFile) {
                        return ((MultipartFile) arg).getOriginalFilename() + " (文件)";
                    }
                    // 其他类型直接保留（可继续扩展过滤规则）
                    return arg;
                })
                .toArray();

        return JSON.toJSONString(serializableArgs);
    }
}
